/**
*
*	multi_clip.c - Modulo que crear N clipboards y los gestiona
*
*/

#include "multi_clip.h"

/* Licencia del modulo */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alvaro Perez Liaño - Xavier Gallofre Nieva");

/* Asignamos NCLIP_MAX ClipBoards por defecto */
static short NCLIP = NCLIP_MAX;
/* Admitimos como parametro un numero de ClipBoards */
module_param(NCLIP, short, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
MODULE_PARM_DESC(NCLIP, "Numero de Clipboards del modulo. Entre NCLIP_MIN y NCLIP_MAX");

/* Estructura de los ficheros en /proc */
static struct proc_dir_entry *proc_selection;
static struct proc_dir_entry *proc_clipboard;
static struct proc_dir_entry *proc_period;

/* Estructura de los directorios en /proc */
static struct proc_dir_entry *proc_dir;

/* Estructura que simula un padre ficticio para las llamadas a funciones
   de creacion de directorios/entradas sin padre (NULL) */
static struct proc_dir_entry *fake_parent = NULL;

/* task_struct para el kernel thread */
static struct task_struct *ts;

/* task_struct para el kernel thread que crea "PERIOD" */
static struct task_struct *ts_wake;
/* Entero que indica si el thread auxiliar esta funcionando o no */
/* 0 = sin arrancar | 1 = arrancado */
static short active_wake;

/* struct de cada nodo de la lista */
struct clipstruct {
	char *buffer;
	int num_elems;
	int index;
	struct list_head list;
};
/* Lista de clipboards */
LIST_HEAD (list_clipstruct);

/* Clipboard activo, su ocupacion actual y el indice del clipboard*/
static struct clipstruct *active_clip;

/* Struct del clipboard "selection" */
static struct clipstruct *selection_clip;

/* Struct del clipboard "periodo" */
static struct clipstruct *period_clip;

/* Buffer auxiliar para poder dar formato a la entrada del usuario*/
static char *my_buffer;

/**************************** INICIO FUNCIONES DEL KERNEL THREAD ****************************/ 
/* Thread principal */
int kernel_func(void *data) {
	allow_signal(SIGCONT);
	allow_signal(SIGKILL);
	allow_signal(SIGSTOP);
	printk(KERN_ALERT "Iniciado el kernel thread: Control_Clipboards.\n");
	set_current_state(TASK_INTERRUPTIBLE);	
	schedule();
	while (!kthread_should_stop()){ 
		printk(KERN_ALERT "Indice clipboard activo: %i\n", active_clip->index);
		printk(KERN_ALERT "Contenido del buffer: %s\n", active_clip->buffer);
		printk(KERN_ALERT "Numero de caracteres del buffer: %i\n", active_clip->num_elems);
		set_current_state(TASK_INTERRUPTIBLE);	
		schedule();
	}
	printk(KERN_ALERT "Finalizado el kernel thread: Control_Clipboards.\n");
	return 0;
}

/* Thread creado al escribir en "PERIOD" */
int kernel_func_wake(void *data) {
	allow_signal(SIGCONT);
	allow_signal(SIGKILL);
	allow_signal(SIGSTOP);
	printk(KERN_ALERT "Iniciado el kernel thread: Despierta kernel cada 10s.\n");
	while (!kthread_should_stop()){ 		
		wake_up_process(ts);
		// Permitimos que se interrumpa la espera
		msleep_interruptible(10000);
		//msleep(10000);
	}
	printk(KERN_ALERT "Finalizado el kernel thread: Despierta kernel cada 10s.\n");
	return 0;
}
/**************************** FIN FUNCIONES DEL KERNEL THREAD *******************************/ 

/**************************** INICIO FUNCION CAMBIO CLIPBOARD **********************************/
/* Cambio del clipboard activo */
void change_clip(int i) {
	struct list_head *pos;
	struct clipstruct *obj;
	list_for_each(pos, &list_clipstruct) {
		obj = list_entry(pos, struct clipstruct, list);
		//printk(KERN_ALERT "Cliboard: %i\n",obj->index);
		if (obj->index == i) {
			active_clip = obj;
			return;
		}
	}
	// Si no existe el clip en la lista, lo creamos
	obj = (struct clipstruct *) vmalloc( sizeof(struct clipstruct));
	obj->index = i;
	obj->num_elems = 0;
	obj->buffer = (char *)vmalloc(MAX_LENGTH);
	list_add(&obj->list, &list_clipstruct);	
	active_clip = obj;
	return;
}
/**************************** FIN FUNCION CAMBIO CLIPBOARD *************************************/

/**************************** INICIO FUNCIONES LECTURA/ESCRITURA *******************************/ 
/* Funcion de lectura del clipboard activo */
int read_proc_clipboard(char *buffer, char **buffer_location, off_t offset, int buffer_length, int *eof, void *data)
{
	if (offset > 0) {
		return 0;
	} else {
		memcpy(buffer, active_clip->buffer, active_clip->num_elems);			
	}
	return active_clip->num_elems;
}

/* Funcion de lectura del clipboard "selection" */
int read_proc_selection(char *buffer, char **buffer_location, off_t offset, int buffer_length, int *eof, void *data)
{
	if (offset > 0) {
		return 0;
	} else {
		memcpy(buffer, selection_clip->buffer, selection_clip->num_elems);	
	}
	return selection_clip->num_elems;
}

/* Funcion de lectura del clipboard "periodo" */
int read_proc_period(char *buffer, char **buffer_location, off_t offset, int buffer_length, int *eof, void *data)
{
	if (offset > 0) {
		return 0;
	} else {
		memcpy(buffer, period_clip->buffer, period_clip->num_elems);	
	}
	return period_clip->num_elems;
}

/* Funcion de escritura del clipboard activo */
int write_proc_clipboard(struct file *filepointer, const char __user *buffer, unsigned long len, void *data)
{
	if (len > MAX_LENGTH) {
		active_clip->num_elems = MAX_LENGTH;
	} else {
		active_clip->num_elems = len;
	}
	// Escritura en el clip
	if (copy_from_user(my_buffer, buffer, active_clip->num_elems)) {
		printk(KERN_ALERT "Imposible escribir\n");
		return -EFAULT;
	}
	
	// printk(KERN_ALERT "Leido en my_buffer %s\n", my_buffer);
	// Limpiamos la cadena por si acaso
	sscanf(my_buffer, "%s", active_clip->buffer);
	// Si el thread secundario esta arrancado no llamamos al thread principal
	if (active_wake == 0)
		wake_up_process(ts);
	return active_clip->num_elems;
}

/* Funcion de escritura del clipboard "periodo" */
int write_proc_period(struct file *filepointer, const char __user *buffer, unsigned long len, void *data)
{
	long i = simple_strtol(buffer, NULL, 10);	
	if (len > MAX_LENGTH) {
		period_clip->num_elems = MAX_LENGTH;
	} else {
		period_clip->num_elems = len;
	}
	
	// Escritura en el clip 
	if (copy_from_user(my_buffer, buffer, period_clip->num_elems)) {
		printk(KERN_ALERT "Imposible escribir\n");
		return -EFAULT;
	}
	
	// printk(KERN_ALERT "Leido en my_buffer %s\n", my_buffer);
	sscanf(my_buffer, "%s", period_clip->buffer);
	/* Si escribimos algo distinto de 0 y el thread no esta arrancado, lo arrancamos */
	if (i != 0 && active_wake == 0) {
		ts_wake = kthread_run(kernel_func_wake,NULL,"Control_Clipboards");
		active_wake = 1;
	/* Si escribimos 0 y el thread esta arrancado, lo paramos */
	} else if (active_wake != 0 && i== 0) {
		kthread_stop(ts_wake);
		active_wake = 0;
	}
	return period_clip->num_elems;
}

/* Funcion de escritura de la entrada "selection" */
int write_proc_selection(struct file *filepointer, const char __user *buffer, unsigned long len, void *data)
{
	//Comprobamos que el Clipboard pedido este entre los valores validos de NCLIP
	long i = simple_strtol(buffer, NULL, 10);
	if (i > NCLIP || i < NCLIP_MIN) {
		printk(KERN_ALERT "NCLIP tiene que estar entre %i y %i\n", NCLIP_MIN, NCLIP_MAX);
		return -1;
	// Actualizamos el clipboard activo si metemos otro valor diferente
	} else if (i != active_clip->index) {
		if (len > MAX_LENGTH) {
			selection_clip->num_elems = MAX_LENGTH;
		} else {
			selection_clip->num_elems = len;
		}
		
		// Escritura en el clip		
		if (copy_from_user(my_buffer, buffer, selection_clip->num_elems)) {
			printk(KERN_ALERT "Imposible escribir\n");
			return -EFAULT;
		}
		
		// printk(KERN_ALERT "Leido en my_buffer %s\n", my_buffer);
		sscanf(my_buffer, "%s", selection_clip->buffer);
		// Cambio de clip
		printk(KERN_ALERT "Cambio clip del: %i al %i\n", active_clip->index, (int)i);
		change_clip((int)i);
		// Si el thread secundario esta arrancado no llamamos al thread principal
		if (active_wake == 0)
			wake_up_process(ts);
	}	
	return selection_clip->num_elems;
}
/**************************** FIN FUNCIONES LECTURA/ESCRITURA **********************************/ 

/* Funcion invocada al cargar el modulo */
int modulo_carga(void) 
{
	struct clipstruct *obj;
	// Comprabamos que NCLIP no sea mayor de NCLIP_MAX y menor que NCLIP_MIN
	if (NCLIP > NCLIP_MAX || NCLIP < NCLIP_MIN) {
		printk(KERN_ALERT "NCLIP tiene que estar entre %i y %i\n", NCLIP_MAX, NCLIP_MIN);
		return -1;
	}
	// Creamos el directorio PROC_DIR
	if (create_proc_dir_custom(PROC_DIR, &proc_dir, &fake_parent) == -ENOMEM) return -ENOMEM;
	
	// Creamos la entrada CLIPBOARD
	if (create_proc_entry_custom(CLIPBOARD, &proc_clipboard, &proc_dir) == -ENOMEM) return -ENOMEM;
	// Asociamos funciones de lectura y escritura y damos permisos
	proc_clipboard->read_proc		= read_proc_clipboard;
	proc_clipboard->write_proc 		= write_proc_clipboard;
	proc_clipboard->mode 			= S_IFREG | S_IRUGO;
	
	// Creamos la entrada SELECTION
	if (create_proc_entry_custom(SELECTION_CLIP, &proc_selection, &proc_dir) == -ENOMEM) return -ENOMEM;
	// Asociamos funciones de lectura y escritura y damos permisos
	proc_selection->read_proc		= read_proc_selection;
	proc_selection->write_proc 		= write_proc_selection;
	proc_selection->mode 			= S_IFREG | S_IRUGO;
	
	// Creamos la entrada PERIOD
	if (create_proc_entry_custom(PERIOD_CLIP, &proc_period, &proc_dir) == -ENOMEM) return -ENOMEM;
	// Asociamos funciones de lectura y escritura y damos permisos
	proc_period->read_proc			= read_proc_period;
	proc_period->write_proc 		= write_proc_period;
	proc_period->mode 				= S_IFREG | S_IRUGO;

	

	// Reservamos la memoria del buffer auxiliar
	my_buffer = (char *)vmalloc(MAX_LENGTH);
	
	// Creamos la lista con 1 nodo
	obj = (struct clipstruct *) vmalloc(sizeof(struct clipstruct));
	obj->index = 1;
	// Reservamos memoria maxima del clip activo
	obj->buffer = (char *)vmalloc(MAX_LENGTH);
	obj->num_elems = 0;
	list_add( &obj->list, &list_clipstruct );
	//Asociamos el clipboard activo al que hemos creado de manera inicial
	active_clip = obj;
	
	// Creamos los struct para el periodo y la seleccion
	selection_clip = (struct clipstruct *) vmalloc(sizeof(struct clipstruct));
	// Reservamos memoria maxima del clip
	selection_clip->buffer = (char *)vmalloc(MAX_LENGTH);
	selection_clip->num_elems = 0;	
	
	period_clip = (struct clipstruct *)	vmalloc(sizeof(struct clipstruct));
	// Reservamos memoria maxima del clip
	period_clip->buffer = (char *)vmalloc(MAX_LENGTH);
	period_clip->num_elems = 0;
	
	// Creamos el kernel thread
  	ts = kthread_run(kernel_func,NULL,"Control_Clipboards");
	// El thread secundario esta apagado inicialmente
	active_wake = 0;
	return 0;
} 

/* Funcion invocada al descargar el modulo */
void modulo_descarga(void) 
{
	struct list_head *pos, *q;
	struct clipstruct *tmp;
	
	// Eliminamos la entrada CLIPBOARD
	remove_proc_entry_custom(CLIPBOARD, &proc_clipboard, &proc_dir);
	// Eliminamos la entrada SELECTION
	remove_proc_entry_custom(SELECTION_CLIP, &proc_selection, &proc_dir);
	// Eliminamos la entrada PERIOD
	remove_proc_entry_custom(PERIOD_CLIP, &proc_period, &proc_dir);
	// Eliminamos el directorio PROC_DIR
	remove_proc_dir_custom(PROC_DIR, &proc_dir, &fake_parent);
	// Eliminamos el kernel thread
	kthread_stop(ts);
	// Eliminamos el thread secundario
	if (active_wake == 1)
		kthread_stop(ts_wake);
	/* Limpiamos la lista */
	list_for_each_safe(pos, q, &list_clipstruct) {
		tmp = list_entry(pos, struct clipstruct, list);
		list_del(pos);
		printk(KERN_ALERT "Borrando Clipboard: %i\n", tmp->index);
		vfree(tmp->buffer);
		vfree(tmp);		
	}
	/* Limpiamos las estructuras de los otros clipboards */
	vfree(period_clip->buffer);
	vfree(period_clip);
	vfree(selection_clip->buffer);
	vfree(selection_clip);
	vfree(my_buffer);
}

/* Funciones init y exit */
module_init(modulo_carga);
module_exit(modulo_descarga);